<?php

class ReadUrl{
	var $cookie;
	var $referer;
	var $userAgent;
	var $ips;
	
	var $timeout=15;
	var $block=1;
	var $limit=8192;
	
	function ReadUrl($option=null){
		$this->cookie = '';
		$this->referer = '';
		$this->userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8';
		$this->ips = array();
		if(is_array($option)){
			if(isset($option['referer'])){
				$this->referer=$option['referer'];
			}
			if(isset($option['cookie'])){
				$this->cookie=$option['cookie'];
			}
			if(isset($option['userAgent'])){
				$this->userAgent=$option['userAgent'];
			}
			
		}
		
	}
	
	function set_ip($host, $ip){
		$this->ips[$host] = $ip;
	}
	
	function _ready($url, $method='GET', & $data=''){
		$matches = parse_url($url);
		$host = $matches['host'];
		$path = $matches['path'] ? $matches['path'].'?'.$matches['query'].'#'.$matches['fragment'] : '/';
		$port = !empty($matches['port']) ? $matches['port'] : 80;
		
		if($data){
			 $method='POST';
			 if(!is_string($data)){
				throw new Exception('POST data mast bu a string.');
			 }
		}
		
		$header = "$method $path HTTP/1.1\r\n";
		$header .= "Host: $host\r\n";
		
		//在抓取www.yahoo.com时，因[Accept: */*]不能获取到正确内容，
		//在 Accept当中加入 text/html 才可以正确取到内容，可能是雅虎服务器有判断。
		//但是使用 file_get_contents 也可正确读到雅虎首页的html正常代码。
		//$header .= "Accept: */*\r\n";
		$header .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
		
		$header .= "Accept-Encoding: gzip,deflate\r\n";
		$header .= "Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7\r\n";

		if(!empty($this->referer)) {
			$header .= "Referer: {$this->referer}\r\n";
		}
		$header .= "Accept-Language: zh-cn,zh;q=0.5\r\n";
		if(!empty($this->userAgent)) {
			$header .= "User-Agent: {$this->userAgent}\r\n";
		}
		
		$header .= "Connection: Close\r\n";
		if(!empty($this->cookie)) {
			$header .= "Cookie: {$this->cookie}\r\n";
		}
		
		if($data){
			$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
			$header .= 'Content-Length: '.strlen($data)."\r\n";
			$header .= "Cache-Control: no-cache\r\n";
		}
		
		$header .= "\r\n";
		
		if($data){
			$header .= $data;
		}
		
		return array(
			'url'=>$url,
			'method'=>$method,
			'host'=>$host,
			'port'=>$port,
			'header'=>$header,
			'data'=>$data
		);
	}
	
	function get($url, $fromLocation=''){
		$arr = $this->_ready($url, 'GET');
		if($fromLocation){
			$arr['fromLocation'] = $fromLocation;
		}
		return $this->_read($arr);
	}
	function head($url, $fromLocation=''){
		$arr = $this->_ready($url, 'HEAD');
		if($fromLocation){
			$arr['fromLocation'] = $fromLocation;
		}
		return $this->_read($arr);
	}
	function post($url, $data=''){
		$arr = $this->_ready($url, 'POST', $data);
		return $this->_read($arr);
	}
	
	function _read(& $param){
	
		$time_start=time();
		$host = $param['host'];
		$host = $this->ips[$host]? $this->ips[$host] : $host;
		$fp = @fsockopen($host, $param['port'], $errno, $errstr, $this->timeout);
		
		$result = array('seconds'=>$t, 'state'=>'500', 'header'=>0, 'content'=>'');
		$result['url'] = $param['url'];
		if($param['fromLocation']){
			$result['fromLocation']=$param['fromLocation'];
		}
		
		if($fp){
			$content = '';
			$header = array();
			$state = '';
			
			stream_set_blocking($fp, $this->block);
			stream_set_timeout($fp, $this->timeout);
			@fwrite($fp, $param['header']);
			$status = stream_get_meta_data($fp);
			
			if(!$status['timed_out']) {
				$line = fgets($fp);
				$line=explode(' ', $line);
				$state=$line[1];

				while ( !feof($fp) &&  $line = trim(@fgets($fp)) ){
					$pos = strpos($line, ':');
					$header[ substr($line, 0, $pos) ] = trim(substr($line, $pos+1));
				}
				//print_r($header);
				if($state == '200'){
					if($param['method'] != 'HEAD'){
						while(!feof($fp)) {
							$content .= fread($fp, $this->limit);
						}
						//echo $content;
						//exit;
					}
					
				}else if($header['Location']){
					//遇到30X重定向，根据Location项重新读取URL内容
					$url = $header['Location'];
					$fromLocation = $state.' '.$param['url'];
					switch($param['method']){
						case 'GET' :
							return $this->get($url, $fromLocation);
						case 'POST': 
							return $this->post($url, $param['data']);
						case 'HEAD': 
							return $this->head($url, $fromLocation);
					}
				}
			}else{
				$state = 'timeout';
			}
			@fclose($fp);
			//print_r($header);
			
			if($header['Transfer-Encoding'] == 'chunked'){
				$content = $this->unchunkHttp11($content);
			}
			
			if($header['Content-Encoding'] == 'gzip' && $content){
				if(function_exists('gzdecode')){
					$content = gzdecode($content);
				}else{
					$content = $this->gzdecode($content,null,$error);
					if($error){
						//exit($error);
						$content = 'Error:'.$error;
					}
				}
			}
			
			$result['state'] = $state;
			$result['header'] = $header;
			$result['content'] = $content;
		}
		
		$t=time()- $time_start;
		$result['seconds'] = $t;
		return $result;
	}
	
	function setCookie($cookie){
		$this->cookie = $cookie;
	}
	

	function unchunkHttp11($data) {
		$fp = 0;
		$outData = "";
		while ($fp < strlen($data)) {
			$rawnum = substr($data, $fp, strpos(substr($data, $fp), "\r\n") + 2);
			$fp += strlen($rawnum);
			$rawnum = trim($rawnum);
			if($rawnum){
				//echo 'rawnum:'.$rawnum."\n";
				$num = hexdec($rawnum);
				//echo 'num:'.$num."\n";
				$chunk = substr($data, $fp, $num);
				$outData .= $chunk;
				$fp += strlen($chunk);
			}
		}
		return $outData;
	}

	function gzdecode($data,$maxlength=null,&$error='',&$filename='')
	{
		$len = strlen($data);
		if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) {
			$error = "Not in GZIP format.";
			return null;  // Not GZIP format (See RFC 1952)
		}
		$method = ord(substr($data,2,1));  // Compression method
		$flags  = ord(substr($data,3,1));  // Flags
		if ($flags & 31 != $flags) {
			$error = "Reserved bits not allowed.";
			return null;
		}
		// NOTE: $mtime may be negative (PHP integer limitations)
		$mtime = unpack("V", substr($data,4,4));
		$mtime = $mtime[1];
		$xfl   = substr($data,8,1);
		$os    = substr($data,8,1);
		$headerlen = 10;
		$extralen  = 0;
		$extra     = "";
		if ($flags & 4) {
			// 2-byte length prefixed EXTRA data in header
			if ($len - $headerlen - 2 < 8) {
				return false;  // invalid
			}
			$extralen = unpack("v",substr($data,8,2));
			$extralen = $extralen[1];
			if ($len - $headerlen - 2 - $extralen < 8) {
				return false;  // invalid
			}
			$extra = substr($data,10,$extralen);
			$headerlen += 2 + $extralen;
		}
		$filenamelen = 0;
		$filename = "";
		if ($flags & 8) {
			// C-style string
			if ($len - $headerlen - 1 < 8) {
				return false; // invalid
			}
			$filenamelen = strpos(substr($data,$headerlen),chr(0));
			if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
				return false; // invalid
			}
			$filename = substr($data,$headerlen,$filenamelen);
			$headerlen += $filenamelen + 1;
		}
		$commentlen = 0;
		$comment = "";
		if ($flags & 16) {
			// C-style string COMMENT data in header
			if ($len - $headerlen - 1 < 8) {
				return false;    // invalid
			}
			$commentlen = strpos(substr($data,$headerlen),chr(0));
			if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
				return false;    // Invalid header format
			}
			$comment = substr($data,$headerlen,$commentlen);
			$headerlen += $commentlen + 1;
		}
		$headercrc = "";
		if ($flags & 2) {
			// 2-bytes (lowest order) of CRC32 on header present
			if ($len - $headerlen - 2 < 8) {
				return false;    // invalid
			}
			$calccrc = crc32(substr($data,0,$headerlen)) & 0xffff;
			$headercrc = unpack("v", substr($data,$headerlen,2));
			$headercrc = $headercrc[1];
			if ($headercrc != $calccrc) {
				$error = "Header checksum failed.";
				return false;    // Bad header CRC
			}
			$headerlen += 2;
		}
		// GZIP FOOTER
		$datacrc = unpack("V",substr($data,-8,4));
		$datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF);
		$isize = unpack("V",substr($data,-4));
		$isize = $isize[1];
		// decompression:
		$bodylen = $len-$headerlen-8;
		if ($bodylen < 1) {
			// IMPLEMENTATION BUG!
			return null;
		}
		$body = substr($data,$headerlen,$bodylen);
		$data = "";
		if ($bodylen > 0) {
			switch ($method) {
			case 8:
				// Currently the only supported compression method:
				$data = gzinflate($body,$maxlength);
				break;
			default:
				$error = "Unknown compression method.";
				return false;
			}
		}  // zero-byte body content is allowed
		// Verifiy CRC32
		$crc   = sprintf("%u",crc32($data));
		$crcOK = $crc == $datacrc;
		$lenOK = $isize == strlen($data);
		if (!$lenOK || !$crcOK) {
			$error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.');
			return false;
		}
		return $data;
	}

}
